package cn.selfiot.iotman.view.project;

import cn.selfiot.iotman.core.I18N;
import cn.selfiot.iotman.core.StageController;
import cn.selfiot.iotman.core.StageManager;
import cn.selfiot.iotman.core.StageParams;
import cn.selfiot.iotman.helper.MessageDialogHelper;
import cn.selfiot.iotman.helper.ProjectPersistHelper;
import cn.selfiot.iotman.model.MqttProjectConstants;
import cn.selfiot.iotman.model.MqttProjectConstants.Option;
import cn.selfiot.iotman.model.mqtt.*;
import cn.selfiot.iotman.model.mqtt.ssl.CACertFileModel;
import cn.selfiot.iotman.model.mqtt.ssl.CAKeystoreModel;
import cn.selfiot.iotman.model.mqtt.ssl.SelfSignedCertModel;
import cn.selfiot.iotman.mqtt.MqttClientManager;
import cn.selfiot.iotman.view.ResourceConstants;
import com.panemu.tiwulfx.dialog.MessageDialog;
import com.panemu.tiwulfx.form.*;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.input.KeyCombination;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import java.util.UUID;

/**
 * @author hugo
 * @since 2020/7/31
 */
public class MqttProjectController extends StageController {
    private static final Logger LOGGER = LoggerFactory.getLogger(MqttProjectController.class);
    
    @FXML
    public VBox vbRoot;
    @FXML
    public Button btnTest;
    @FXML
    public Button btnSave;
    @FXML
    public Button btnClose;
    @FXML
    public SplitPane spBody;
    @FXML
    public TabPane tpBody;
    @FXML
    public Form<ProjectModel> frmProject;
    @FXML
    public TextControl txtServerName;
    @FXML
    public TextControl txtBrokerAddr;
    @FXML
    public NumberControl txtBrokerPort;
    @FXML
    public TextControl txtClientId;
    @FXML
    public Button btnGen;
    @FXML
    public Accordion serverProps;
    @FXML
    public Form<ConnParamsModel> connForm;
    @FXML
    public NumberControl numConnectionTimeout;
    @FXML
    public NumberControl keepAlive;
    @FXML
    public CheckBoxControl chkCleanSession;
    @FXML
    public CheckBoxControl chkReconnection;
    @FXML
    public NumberControl numMaxInflight;
    @FXML
    public ComboBoxControl cmbVersion;
    @FXML
    public Form<UserPwdAuthModel> userAuthForm;
    @FXML
    public TextControl txtUsername;
    @FXML
    public TextControl txtPwd;
    @FXML
    public ToggleButton btnEnableSSL;
    @FXML
    public ComboBoxControl cbSSLProtocol;
    @FXML
    public HBox hboxType;
    @FXML
    public RadioButton radioCaCert;
    @FXML
    public RadioButton radioCaKeystore;
    @FXML
    public RadioButton radioSelfCert;
    @FXML
    public Form<CACertFileModel> caCertForm;
    @FXML
    public TextControl txtCaCert;
    @FXML
    public Button btnCaChooser;
    @FXML
    public Form<CAKeystoreModel> caKeystoreForm;
    @FXML
    public TextControl txtKeystore;
    @FXML
    public Button btnKeyStoreChooser;
    @FXML
    public TextControl txtKeystorePwd;
    @FXML
    public Form<SelfSignedCertModel> selfCertForm;
    @FXML
    public TextControl txtCertFile;
    @FXML
    public Button btnCaFileChooser;
    @FXML
    public TextControl txtClientCertFile;
    @FXML
    public Button btnClientCertChooser;
    @FXML
    public TextControl txtClientKey;
    @FXML
    public Button btnClientKeyChooser;
    @FXML
    public TextControl txtClientKeyPwd;
    @FXML
    public CheckBoxControl cbPem;
    @FXML
    public Form topicForm;
    @FXML
    public TextControl txtTopic;
    @FXML
    public Button btnAddTopic;
    @FXML
    public ListView listTopic;
    @FXML
    public Form<LwtModel> lwtForm;
    @FXML
    public Tab tabLwt;
    @FXML
    public ComboBoxControl cbTopic;
    @FXML
    public TextAreaControl txtPayload;
    @FXML
    public ComboBoxControl cmbQos;
    @FXML
    public CheckBoxControl chkRetained;
    @FXML
    public Label lbDesc;
    
    private final StageManager stageManager = StageManager.getInstance();
//    private MqttClientManager mqttClientManager;
    private ResourceBundle bundle;
    private ConnParamsModel connParams;
    private UserPwdAuthModel userPwdAuth;
    private SSLModel ssl;
    private LwtModel lwt;
    private CACertFileModel caCertFile;
    private CAKeystoreModel caKeystore;
    private SelfSignedCertModel selfSignedCert;
    private ProjectModel mqttProject;
    private List<TopicModel> topics;
    private boolean isNew;
    private List<String> topicList = new ArrayList<>();
    
    @Override
    public void initialize(URL location, ResourceBundle resources) {
        bundle = I18N.getBundle();
        StageParams params = this.getParams();
        if (params != null) {
            mqttProject = (ProjectModel) params.get("project");
        }
        
        isNew = mqttProject == null;
        
        initModel();
        initProject();
        initConnParams();
        initSSL();
        initLwt();
        initUserAuth();
        initTopics();
        
        setActionListener();
        
        serverProps.setExpandedPane(serverProps.getPanes().get(0));
        txtServerName.requestFocus();
    }
    
    private void initModel() {
        if (isNew) {
            connParams = new ConnParamsModel();
            userPwdAuth = new UserPwdAuthModel();
            ssl = new SSLModel();
            lwt = new LwtModel();
            topics = new ArrayList<>();
            caCertFile = new CACertFileModel();
            caKeystore = new CAKeystoreModel();
            selfSignedCert = new SelfSignedCertModel();
            mqttProject = new ProjectModel();
        } else {
            connParams = mqttProject.getConnParams();
            if (connParams == null) {
                connParams = new ConnParamsModel();
            }
            userPwdAuth = mqttProject.getUserPwdAuth();
            if (userPwdAuth == null) {
                userPwdAuth = new UserPwdAuthModel();
            }
            
            ssl = mqttProject.getSsl();
            if (ssl != null) {
                caCertFile = ssl.getCaCertFile() == null ? new CACertFileModel() : ssl.getCaCertFile();
                caKeystore = ssl.getCaKeystore() == null ? new CAKeystoreModel() : ssl.getCaKeystore();
                selfSignedCert = ssl.getSelfSignedCert() == null ? new SelfSignedCertModel()
                        : ssl.getSelfSignedCert();
            } else {
                ssl = new SSLModel();
            }
            
            topics = new ArrayList<>(mqttProject.getTopics());
            lwt = mqttProject.getLwt();
            if (lwt == null) {
                lwt = new LwtModel();
            }
        }
    }
    
    private void initProject() {
        frmProject.bindChildren();
        frmProject.setRecord(mqttProject);
    }
    
    private void initConnParams() {
        for (Option option : MqttProjectConstants.MQTT_VER) {
            cmbVersion.addItem(option.getLabel(), String.valueOf(option.getValue()));
        }
        chkCleanSession.setValue(false);
        
        connForm.bindChildren();
        connForm.setRecord(connParams);
    }
    
    private void initUserAuth() {
        userAuthForm.bindChildren();
        userAuthForm.setRecord(userPwdAuth);
    }
    
    private void initSSL() {
        ToggleGroup caRadioGroup = new ToggleGroup();
        radioCaCert.setToggleGroup(caRadioGroup);
        radioCaKeystore.setToggleGroup(caRadioGroup);
        radioSelfCert.setToggleGroup(caRadioGroup);
        
        for (Option option : MqttProjectConstants.SSL_VER) {
            cbSSLProtocol.addItem(option.getLabel(),
                    String.valueOf(option.getValue()));
        }
        
        initSSLPanel();
        
        if (ssl.getSslVersion() == null) {
            cbSSLProtocol.setValue(MqttProjectConstants.SSL_VER[1].getValue());
        } else {
            cbSSLProtocol.setValue(ssl.getSslVersion());
        }
        
        btnEnableSSL.setSelected(ssl.getEnable());
        if (ssl.getEnable()) {
            selectSSL();
            radioCaCert.setSelected(caCertFile != null && caCertFile.getFilePath() != null);
            radioCaKeystore.setSelected(caKeystore != null && caKeystore.getFilePath() != null);
            radioSelfCert.setSelected(selfSignedCert != null && selfSignedCert.getCaFile() != null);
            sslToggle();
        }
        
        caCertForm.bindChildren();
        caCertForm.setRecord(caCertFile);
        
        caKeystoreForm.bindChildren();
        caKeystoreForm.setRecord(caKeystore);
        
        selfCertForm.bindChildren();
        selfCertForm.setRecord(selfSignedCert);
    }
    
    private void initSSLPanel() {
        setPanelVisible(hboxType, false);
        setPanelVisible(caCertForm, false);
        setPanelVisible(caKeystoreForm, false);
        setPanelVisible(selfCertForm, false);
        
        btnEnableSSL.setSelected(false);
        radioCaCert.selectedProperty().setValue(false);
        radioCaKeystore.selectedProperty().setValue(false);
        radioSelfCert.selectedProperty().setValue(false);
    }
    
    private void initTopics() {
//        topicForm.bindChildren();
        listTopic.setContextMenu(createContextMenu());
        List<String> strList = new ArrayList<>();
        topics.forEach(topic -> strList.add(topic.getTopicString()));
        listTopic.getItems().clear();
        listTopic.setItems(FXCollections.observableArrayList(strList));
    }
    
    private void initLwt() {
        for (Option option : MqttProjectConstants.QOS) {
            cmbQos.addItem(option.getLabel(),
                    Integer.valueOf(String.valueOf(option.getValue())));
        }
        List<TopicModel> topicModels = mqttProject.getTopics();
        if (topicModels == null) topicModels = new ArrayList<>();
        cbTopic.getItems().clear();
        topicModels.forEach(topicModel -> {
            String topic = topicModel.getTopicString();
            cbTopic.addItem(topic, topic);
        });
        
        chkRetained.setValue(false);
        
        lwtForm.bindChildren();
        lwtForm.setRecord(lwt);
    }
    
    private void setActionListener() {
        btnTest.setOnAction(actionEvent -> test());
        btnSave.setOnAction(actionEvent -> save());
        btnClose.setOnAction(actionEvent -> close());
        
        btnEnableSSL.setOnAction(actionEvent -> selectSSL());
        radioCaCert.setOnAction(actionEvent -> sslToggle());
        radioCaKeystore.setOnAction(actionEvent -> sslToggle());
        radioSelfCert.setOnAction(actionEvent -> sslToggle());
        
        FileChooser fileChooser = new FileChooser();
        btnCaChooser.setOnAction(actionEvent -> {
            File selectedFile = fileChooser.showOpenDialog(getStage());
            if (selectedFile != null) {
                txtCaCert.setValue(selectedFile.getAbsolutePath());
            } else {
                LOGGER.info("cancel select file.");
            }
        });
        
        btnKeyStoreChooser.setOnAction(actionEvent -> {
            File selectedFile = fileChooser.showOpenDialog(getStage());
            if (selectedFile != null) {
                txtKeystore.setValue(selectedFile.getAbsolutePath());
            } else {
                LOGGER.info("cancel select file.");
            }
        });
        
        btnCaFileChooser.setOnAction(actionEvent -> {
            File selectedFile = fileChooser.showOpenDialog(getStage());
            if (selectedFile != null) {
                txtCertFile.setValue(selectedFile.getAbsolutePath());
            } else {
                LOGGER.info("cancel select file.");
            }
        });
        
        btnClientCertChooser.setOnAction(actionEvent -> {
            File selectedFile = fileChooser.showOpenDialog(getStage());
            if (selectedFile != null) {
                txtClientCertFile.setValue(selectedFile.getAbsolutePath());
            } else {
                LOGGER.info("cancel select file.");
            }
        });
        
        btnClientKeyChooser.setOnAction(actionEvent -> {
            File selectedFile = fileChooser.showOpenDialog(getStage());
            if (selectedFile != null) {
                txtClientKey.setValue(selectedFile.getAbsolutePath());
            } else {
                LOGGER.info("cancel select file.");
            }
        });
        
        btnAddTopic.setOnAction(event -> {
            String topic = txtTopic.getValue();
            listTopic.getItems().add(topic);
            
            cbTopic.addItem(topic, topic);
        });
        
        btnGen.setOnAction(event -> {
            String genCode = UUID.randomUUID().toString().toUpperCase();
            txtClientId.setValue(genCode);
        });
    }
    
    private void updateDesc(String desc) {
        Platform.runLater(() -> lbDesc.setText(desc));
    }
    
    private void sslToggle() {
        selectCaCert();
        selectCaKeystore();
        selectSelfCert();
    }
    
    private void selectSSL() {
        boolean isSelected = btnEnableSSL.isSelected();
        setPanelVisible(hboxType, isSelected);
        if (!isSelected) {
            initSSLPanel();
        }
    }
    
    private void setPanelVisible(Pane pane, boolean visible) {
        pane.setVisible(visible);
        pane.setManaged(visible);
    }
    
    private void selectCaCert() {
        boolean isSelected = radioCaCert.isSelected();
        setPanelVisible(caCertForm, isSelected);
    }
    
    private void selectCaKeystore() {
        boolean isSelected = radioCaKeystore.isSelected();
        setPanelVisible(caKeystoreForm, isSelected);
    }
    
    private void selectSelfCert() {
        boolean isSelected = radioSelfCert.isSelected();
        setPanelVisible(selfCertForm, isSelected);
    }
    
    private ProjectModel getResult() {
        mqttProject = frmProject.getRecord();
        connParams = connForm.getRecord();
        userPwdAuth = userAuthForm.getRecord();
        lwt = lwtForm.getRecord();
        
        caCertFile = caCertForm.getRecord();
        caKeystore = caKeystoreForm.getRecord();
        selfSignedCert = selfCertForm.getRecord();
        
        ssl.setEnable(btnEnableSSL.isSelected());
        if (btnEnableSSL.isSelected()) {
            String protocolVersion = (String) cbSSLProtocol.getValue();
            ssl.setSslVersion(protocolVersion);
            
            if (radioCaCert.isSelected()) {
                ssl.setCaCertFile(caCertFile);
            }
            
            if (radioCaKeystore.isSelected()) {
                ssl.setCaKeystore(caKeystore);
            }
            
            if (radioSelfCert.isSelected()) {
                ssl.setSelfSignedCert(selfSignedCert);
            }
        } else {
            CACertFileModel caCertFile = ssl.getCaCertFile();
            if (caCertFile != null) {
                ssl.setCaCertFile(null);
            }
            
            CAKeystoreModel caKeystore = ssl.getCaKeystore();
            if (caKeystore != null) {
                ssl.setCaCertFile(null);
            }
            
            SelfSignedCertModel selfSignedCert = ssl.getSelfSignedCert();
            if (selfSignedCert != null) {
                ssl.setSelfSignedCert(null);
            }
        }
        // TODO 未通过测试
//        mqttProject.setSsl(ssl);
        
        ObservableList<String> observableList = listTopic.getItems();
        List<String> topicString = new ArrayList<>(observableList);
        
        List<TopicModel> listTopics = new ArrayList<>();
        topicString.forEach(t -> {
            TopicModel topic = new TopicModel();
            topic.setTopicString(t);
            listTopics.add(topic);
        });
        mqttProject.setTopics(listTopics);
        
        mqttProject.setConnParams(connParams);
        mqttProject.setUserPwdAuth(userPwdAuth);
        mqttProject.setLwt(lwt);
        if (mqttProject.getId() == null) {
            mqttProject.setId(UUID.randomUUID().toString());
        }
        
        return mqttProject;
    }
    
    private void test() {
        Platform.runLater(() -> {
            ProjectModel project = getResult();
            LOGGER.info("save project:{}", project);
            MqttClientManager mqttClientManager = MqttClientManager.getInstance(project);
            try {
                mqttClientManager.connect(mqttCallback);
            } catch (MqttException e) {
                MessageDialog.Answer answer = MessageDialogHelper.confirm(
                        this.getStage()
                        , bundle.getString("dialog.test.fail"));
                if (answer == MessageDialog.Answer.YES_OK
                        || answer == MessageDialog.Answer.CANCEL) {
                    try {
                        MqttClientManager.disconnect();
                    } catch (MqttException mqttException) {
                        LOGGER.error(mqttException.getMessage());
                    }
                }
            }
        });
    }
    
    private void save() {
        Platform.runLater(() -> {
            ProjectModel project = getResult();
//            LOGGER.info("save project:{}", project);
            ProjectPersistHelper.saveProject(project);
            
            this.close();
        });
    }
    
    private void close() {
        this.getStage().close();
        this.stageManager.callback(ResourceConstants.MAIN_VIEW);
    }
    
    @Override
    public void callback() {
    
    }
    
    private ContextMenu createContextMenu() {
        ContextMenu menu = new ContextMenu();
        
        MenuItem itemRemove = new MenuItem(bundle.getString("menu.topics.remove"));
        itemRemove.setAccelerator(KeyCombination.keyCombination("CTRL+X"));
        itemRemove.setOnAction(event -> {
            int selected = listTopic.getSelectionModel().getSelectedIndex();
            listTopic.getItems().remove(selected);
            event.consume();
        });
        
        menu.getItems().addAll(itemRemove);
        return menu;
    }
    
    // MQTT监听并且接受消息
    private final MqttCallbackExtended mqttCallback = new MqttCallbackExtended() {
        @Override
        public void connectComplete(boolean reconnect, String serverURI) {
            Platform.runLater(() -> {
                MessageDialog.Answer answer = MessageDialogHelper.confirm(
                        getStage()
                        , bundle.getString("dialog.test.ok"));
                if (answer == MessageDialog.Answer.YES_OK
                        || answer == MessageDialog.Answer.CANCEL) {
                    try {
                        MqttClientManager.disconnect();
                    } catch (MqttException mqttException) {
                        LOGGER.error(mqttException.getMessage());
                    }
                }
            });
            
        }
        
        @Override
        public void messageArrived(String topic, MqttMessage message) {
            LOGGER.info("message arrived...");
        }
        
        @Override
        public void deliveryComplete(IMqttDeliveryToken token) {
            LOGGER.info("delivery complete.");
        }
        
        @Override
        public void connectionLost(Throwable arg0) {
            LOGGER.warn("connection lost ...");
        }
    };
}
